package com.atguigu.test.interview;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

public class CalcCOS {

    public static void main(String[] args) {
        String path = "C:\\Users\\xr-8\\Desktop\\test.txt";
        printResult(dataMatch(importTxt(path)));
    }

    /**
     * 数据导入，并生成初始List<DTO>
     * @param path
     * @return
     */
    public static List<CaseDTO> importTxt(String path) {
        FileInputStream fis = null;
        Scanner scanner = null;
        int lineNum = 0;
        try {
            fis = new FileInputStream(new File(path));
            scanner= new Scanner(fis);
            String line;
            CaseDTO dto;
            List<CaseDTO> data = new ArrayList<>();
            while (scanner.hasNextLine()) {
                lineNum++;
                if (lineNum == 1) {
                    line = scanner.nextLine();
                    continue;
                }
                line = scanner.nextLine();
                System.out.println(line);
                if (line!=null && !"".equals(line)) {
                    String []arr = line.split("\t");
                    if (arr.length == 3) {
                        dto = new CaseDTO(Integer.valueOf(arr[0]), Integer.valueOf(arr[1]), Float.parseFloat(arr[2]));
                        data.add(dto);
                    }
                }
            }
            return data;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            try {
                if(fis!=null)fis.close();
            } catch (IOException e) {
            }
            if(scanner!=null)scanner.close();
        }
        return null;
    }

    /**
     * case两两匹配并计算
     * @param data
     * @return
     */
    public static Map<Integer, LinkedList<CaseMatchCos>> dataMatch(List<CaseDTO> data) {
        if(data == null || data.size()<=0) {
            return null;
        }
        // 按照caseId分组，转成Map<caseId, List<CaseDTO>>
        Map<Integer, List<CaseDTO>> changeDataMap = data.stream().collect(Collectors.groupingBy(CaseDTO::getCaseId));
        if(changeDataMap == null || changeDataMap.size()<=0) {
            return null;
        }

        // 再两两匹配成caseMatchList，计算cos后放入allTopCos
        CaseMatchCos caseMatchCos;
        Map<Integer, LinkedList<CaseMatchCos>> allTopCos = new TreeMap<>();

        List<CaseMatch> caseMatchList;
        CaseMatch caseMatch;

        Set<Integer> caseIdSet = changeDataMap.keySet();
        Object[] caseIdArray = caseIdSet.toArray();
        for (int i = 0; i < caseIdArray.length; i++) {
            for (int j = 0; j < caseIdArray.length; j++) {
                Integer caseId1 = (Integer) caseIdArray[i];
                Integer caseId2 = (Integer) caseIdArray[j];
                if (i == j) { // 如果是自己，则直接放入allTopCos
                    caseMatchCos = new CaseMatchCos(caseId1, caseId2, null, 1f);
                    addTopCos(allTopCos, caseMatchCos);
                } else {  // 如果是其它，则合并，计算cos后再放入allTopCos
                    // 合并tagId
                    List<CaseDTO> caseDTOList1 = changeDataMap.get(caseId1);
                    List<CaseDTO> caseDTOList2 = changeDataMap.get(caseId2);
                    Set<Integer> tagIdSet1 = caseDTOList1.stream().map(CaseDTO::getTagId).collect(Collectors.toSet());
                    Set<Integer> tagIdSet2 = caseDTOList2.stream().map(CaseDTO::getTagId).collect(Collectors.toSet());
                    tagIdSet1.addAll(tagIdSet2);
                    caseMatchList = new ArrayList<>();
                    // 根据tagId，组合caseMatchList
                    for (Integer tagId : tagIdSet1) {
                        Float weight1 = getWeightByTagId(tagId, caseDTOList1);
                        Float weight2 = getWeightByTagId(tagId, caseDTOList2);
                        caseMatch = new CaseMatch(tagId, caseId1, caseId2, weight1, weight2);
                        caseMatchList.add(caseMatch);
                    }
                    // 根据匹配后的数据，计算对应的cos值
                    caseMatchCos = new CaseMatchCos(caseId1, caseId2, caseMatchList, 0f);
                    addTopCos(allTopCos, calc(caseMatchCos));
                }

            }
        }
        return allTopCos;
    }

    /**
     * 计算cos值
     * @param caseMatchCos
     * @return
     */
    public static CaseMatchCos calc(CaseMatchCos caseMatchCos) {
        if (Objects.isNull(caseMatchCos) || Objects.isNull(caseMatchCos.getCaseMatchList())) {
            return caseMatchCos;
        }
        BigDecimal numerator = BigDecimal.ZERO;
        BigDecimal denominator1 = BigDecimal.ZERO;
        BigDecimal denominator2 = BigDecimal.ZERO;
        for (CaseMatch cm : caseMatchCos.getCaseMatchList()) {
            numerator = numerator.add(BigDecimal.valueOf(cm.getWeight1()).multiply(BigDecimal.valueOf(cm.getWeight2())));
            denominator1 = denominator1.add(BigDecimal.valueOf(cm.getWeight1()).multiply(BigDecimal.valueOf(cm.getWeight1())));
            denominator2 = denominator2.add(BigDecimal.valueOf(cm.getWeight2()).multiply(BigDecimal.valueOf(cm.getWeight2())));
        }

        if (denominator1.compareTo(BigDecimal.ZERO) == 0 || denominator2.compareTo(BigDecimal.ZERO) == 0) {
            return caseMatchCos;
        }
        BigDecimal bigDecimal1 = BigDecimal.valueOf(Math.sqrt(denominator1.doubleValue()));
        BigDecimal bigDecimal2 = BigDecimal.valueOf(Math.sqrt(denominator2.doubleValue()));

        caseMatchCos.setCosValue(numerator.divide(bigDecimal1.multiply(bigDecimal2), 6, BigDecimal.ROUND_HALF_UP).floatValue());

        return caseMatchCos;
    }


    /**
     * 维护某个caseId的top3的List
     * @param allTopCos
     * @param caseMatchCos
     */
    public static void addTopCos(Map<Integer, LinkedList<CaseMatchCos>> allTopCos, CaseMatchCos caseMatchCos){
        if(Objects.isNull(caseMatchCos) || caseMatchCos.getCaseId1() <= 0 || caseMatchCos.getCosValue() <= 0) {
            return;
        }
        LinkedList<CaseMatchCos> caseMatchCosList = allTopCos.get(caseMatchCos.getCaseId1());
        if(caseMatchCosList == null ) {
            caseMatchCosList = new LinkedList<>();
            caseMatchCosList.add(caseMatchCos);
            allTopCos.put(caseMatchCos.getCaseId1(), caseMatchCosList);
            return;
        }
        if (caseMatchCosList.size() < 3) {
            caseMatchCosList.add(caseMatchCos);//
            caseMatchCosList.sort(Comparator.comparingDouble(CaseMatchCos::getCosValue).reversed());
            return;
        }
        if(caseMatchCos.getCosValue() > caseMatchCosList.getLast().getCosValue()) {
            caseMatchCosList.removeLast();
            caseMatchCosList.addLast(caseMatchCos);
            caseMatchCosList.sort(Comparator.comparingDouble(CaseMatchCos::getCosValue).reversed());
        }
    }


    /**
     * 根据caseId 和 tagId 获取对应的weight的值
     * @param tagId
     * @param caseDTOList
     * @return
     */
    public static Float getWeightByTagId(Integer tagId, List<CaseDTO> caseDTOList) {
        Float weight;
        CaseDTO case1 = caseDTOList.stream().filter(e->e.getTagId().intValue() == tagId.intValue()).findFirst().orElse(null);
        if (Objects.isNull(case1) || Objects.isNull(case1.getWeight())) {
            weight = 0f;
        } else {
            weight = case1.getWeight();
        }
        return weight;
    }

    /**
     * 输出结果
     * @param allTopCos
     */
    public static void printResult(Map<Integer, LinkedList<CaseMatchCos>> allTopCos) {
        if (Objects.isNull(allTopCos) || allTopCos.size() <= 0) {
            System.out.println("===no result===");
            return;
        }
        Set<Integer> keys = allTopCos.keySet();
        System.out.println("caseid caseid weight");
        for (Integer caseId: keys) {
            LinkedList<CaseMatchCos> caseMatchCos = allTopCos.get(caseId);
            for(CaseMatchCos cmc : caseMatchCos) {
                System.out.println(caseId + " " + cmc.getCaseId2() + " " + cmc.getCosValue());
            }
        }
    }

}

/**
 * 存放导入后的原始数据
 */
class CaseDTO {
    private Integer caseId;
    private Integer tagId;
    private Float weight;

    public CaseDTO(Integer caseId, Integer tagId, Float weight) {
        this.caseId = caseId;
        this.tagId = tagId;
        this.weight = weight;
    }

    public Integer getCaseId() {
        return caseId;
    }

    public void setCaseId(Integer caseId) {
        this.caseId = caseId;
    }

    public Integer getTagId() {
        return tagId;
    }

    public void setTagId(Integer tagId) {
        this.tagId = tagId;
    }

    public Float getWeight() {
        return weight;
    }

    public void setWeight(Float weight) {
        this.weight = weight;
    }
}

/**
 * 两两匹配的数据实体类
 */
class CaseMatch {
    private Integer tagId;
    private Integer caseId1 = 0;
    private Integer caseId2 = 0;
    private Float weight1 = 0f;
    private Float weight2 = 0f;

    public CaseMatch(Integer tagId, Integer caseId1, Integer caseId2, Float weight1, Float weight2) {
        this.tagId = tagId;
        this.caseId1 = caseId1;
        this.caseId2 = caseId2;
        this.weight1 = weight1;
        this.weight2 = weight2;
    }

    public Integer getTagId() {
        return tagId;
    }

    public void setTagId(Integer tagId) {
        this.tagId = tagId;
    }

    public Integer getCaseId1() {
        return caseId1;
    }

    public void setCaseId1(Integer caseId1) {
        this.caseId1 = caseId1;
    }

    public Integer getCaseId2() {
        return caseId2;
    }

    public void setCaseId2(Integer caseId2) {
        this.caseId2 = caseId2;
    }

    public Float getWeight1() {
        return weight1;
    }

    public void setWeight1(Float weight1) {
        this.weight1 = weight1;
    }

    public Float getWeight2() {
        return weight2;
    }

    public void setWeight2(Float weight2) {
        this.weight2 = weight2;
    }

}

/**
 * 匹配后的数据计算实体类
 */
class CaseMatchCos{
    private Integer caseId1 = 0;
    private Integer caseId2 = 0;
    private List<CaseMatch> caseMatchList;
    private Float cosValue = 0f;

    public CaseMatchCos(Integer caseId1, Integer caseId2, List<CaseMatch> caseMatchList, Float cosValue) {
        this.caseId1 = caseId1;
        this.caseId2 = caseId2;
        this.caseMatchList = caseMatchList;
        this.cosValue = cosValue;
    }

    public Integer getCaseId1() {
        return caseId1;
    }

    public void setCaseId1(Integer caseId1) {
        this.caseId1 = caseId1;
    }

    public Integer getCaseId2() {
        return caseId2;
    }

    public void setCaseId2(Integer caseId2) {
        this.caseId2 = caseId2;
    }

    public List<CaseMatch> getCaseMatchList() {
        return caseMatchList;
    }

    public void setCaseMatchList(List<CaseMatch> caseMatchList) {
        this.caseMatchList = caseMatchList;
    }

    public Float getCosValue() {
        return cosValue;
    }

    public void setCosValue(Float cosValue) {
        this.cosValue = cosValue;
    }
}
